﻿using System;
using System.Collections.Generic;
using System.Linq;
using Lightray.Hattrick.ChppLib.EntityTypes;

namespace Lightray.Hattrick.ChppLib
{
    public class DataCache
    {
        private Dictionary<int, Team> _teams;
        private Dictionary<int, Player> _players;
        private Dictionary<int, LeagueLevelUnit> _leagueUnits;
        private Dictionary<int, LeagueCountry> _leagueCountries;
        private Dictionary<int, Region> _regions;
        private Dictionary<int, Match> _matches;
        private Dictionary<int, TeamLeagueStats> _teamLeagueStats;
        private Dictionary<int, DetailedMatch> _liveMatches;

        public Economy MyEconomy { get; internal set; }
        public IEnumerable<Team> Teams { get { return _teams.Values; } }
        public IEnumerable<Player> Players { get { return _players.Values; } }
        public IEnumerable<LeagueLevelUnit> LeagueUnits { get { return _leagueUnits.Values; } }
        public IEnumerable<LeagueCountry> LeagueCountries { get { return _leagueCountries.Values; } }
        public IEnumerable<Region> Regions { get { return _regions.Values; } }
        public IEnumerable<Match> Matches { get { return _matches.Values; } }
        public IEnumerable<TeamLeagueStats> TeamLeagueStats { get { return _teamLeagueStats.Values; } } 

        public DataCache()
        {
            _teams = new Dictionary<int, Team>();
            _players = new Dictionary<int, Player>();
            _leagueUnits = new Dictionary<int, LeagueLevelUnit>();
            _leagueCountries = new Dictionary<int, LeagueCountry>();
            _regions = new Dictionary<int, Region>();
            _matches = new Dictionary<int, Match>();
            _teamLeagueStats = new Dictionary<int, TeamLeagueStats>();
            _liveMatches = new Dictionary<int, DetailedMatch>();
        }

        public void Add<T>(T item)
        {
            if (item == null)
                throw new ArgumentException("item cannot be null", "item");

            if (item is Team)
            {
                Team team = item as Team;
                _teams[team.ID] = team;
            }
            else if (item is Player)
            {
                Player player = item as Player;
                _players[player.ID] = player;
            }
            else if (item is LeagueLevelUnit)
            {
                LeagueLevelUnit leagueLevelUnit = item as LeagueLevelUnit;
                _leagueUnits[leagueLevelUnit.ID] = leagueLevelUnit;
            }
            else if (item is LeagueCountry)
            {
                LeagueCountry leagueCountry = item as LeagueCountry;
                _leagueCountries[leagueCountry.LeagueID] = leagueCountry;
            }
            else if (item is Region)
            {
                Region region = item as Region;
                _regions[region.ID] = region;
            }
            else if (item is Match)
            {
                Match match = item as Match;
                _matches[match.ID] = match;
            }
            else if (item is TeamLeagueStats)
            {
                TeamLeagueStats teamLeagueStats = item as TeamLeagueStats;
                _teamLeagueStats[teamLeagueStats.TeamID] = teamLeagueStats;
            }
            else
            {
                throw new ArgumentException("Invalid item type", "item");
            }            
        }

        public void AddRange<T>(IEnumerable<T> items)
        {
            if (items == null)
                throw new ArgumentException("items cannot be null", "items");

            foreach (var item in items)
                Add(item);
        }

        internal Team TeamById(int teamId)
        {
            if (_teams.ContainsKey(teamId))
                return _teams[teamId];
            else
                return null;            
        }

        internal TeamLeagueStats LeagueStatsByTeamID(int teamId)
        {
            if (_teamLeagueStats.ContainsKey(teamId))
                return _teamLeagueStats[teamId];
            else
                return null;            
        }

        internal LeagueCountry CountryByLeagueID(int leagueId)
        {
            if (_leagueCountries.ContainsKey(leagueId))
                return _leagueCountries[leagueId];
            else
                return null;            
        }     

        internal Region RegionByRegionID(int regionId)
        {
            if (_regions.ContainsKey(regionId))
                return _regions[regionId];
            else
                return null;
        }

        internal LeagueLevelUnit LeagueUnitByID(int leagueUnitId)
        {
            if (_leagueUnits.ContainsKey(leagueUnitId))
                return _leagueUnits[leagueUnitId];
            else
                return null;
        }

        internal EntityCollection<Player> PlayersByTeamID(int teamId)
        {
            return (from player in Players
                    where player.TeamID == teamId
                    select player).ToEntityCollection();
        }

        internal EntityCollection<Match> MatchesByTeamId(int teamId)
        {
            return (from match in Matches
                    where match.HomeTeamId == teamId || match.AwayTeamId == teamId
                    select match).ToEntityCollection();
        }

        internal void UpdateLive(IEnumerable<DetailedMatch> liveMatches)
        {
            foreach (var liveMatch in liveMatches)
            {
                if (matchAlreadyCached(liveMatch) && !matchDeatilsAreFull(liveMatch))
                    appendNewEvents(_liveMatches[liveMatch.ID], liveMatch);
                else
                    _liveMatches[liveMatch.ID] = liveMatch;                
            }
        }

        private bool matchAlreadyCached(DetailedMatch liveMatch)
        {
            return _liveMatches.ContainsKey(liveMatch.ID);
        }

        private bool matchDeatilsAreFull(DetailedMatch liveMatch)
        {
            // TODO: Assuming the match details are full if the first event of the match (with index: 0) is included.
            return liveMatch.Events.Count > 0 && liveMatch.Events[0].Index == 0;
        }

        private void appendNewEvents(DetailedMatch cachedMatch, DetailedMatch updatedMatch)
        {
            // TODO: the place to update other deatils, like ball possesion after half time

            // append new events with old ones disregarding duplicates
            List<MatchEvent> totalEvents = new List<MatchEvent>();
            totalEvents.AddRange(cachedMatch.Events);
            totalEvents.AddRange(updatedMatch.Events);
            var distinctEvents = from matchEvent in totalEvents 
                                 group matchEvent by matchEvent.Index into groupedEvents
                                 select groupedEvents.First();

            // updated cached match
            cachedMatch.ReplaceEvents(distinctEvents);
        }
    }
}
